package cn.xw.utils;

import com.wf.captcha.ArithmeticCaptcha;
import com.wf.captcha.ChineseCaptcha;
import com.wf.captcha.GifCaptcha;
import com.wf.captcha.SpecCaptcha;
import com.wf.captcha.base.Captcha;
import lombok.Data;
import lombok.Getter;
import lombok.Setter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import java.awt.*;
import java.io.IOException;
import java.util.UUID;

/**
 * 简单验证码工具类
 * 验证码工具类，使用前需注意：（基于easy-captcha项目简单封装）
 * 导入Maven坐标：com.github.whvcse » easy-captcha   version：1.6.2
 * 导入gradle坐标：compile 'com.github.whvcse:easy-captcha:1.6.2'
 *
 * @author AnHui OuYang
 * @version 1.0
 */
@Data
public class SimpleCaptchaUtils {

    private final Logger log = LoggerFactory.getLogger(this.getClass());
    // 前后端不分离是Session的名称
    public static final String SESSION_ATTR_NAME = "captcha";

    // 验证码宽度
    private Integer width = 130;
    // 验证码高度
    private Integer height = 48;
    // 简单验证码类型：1(字母数字混合)、2(纯数字)、3(纯字母)
    private Integer simpTextType = 1;
    // 简单验证码长度
    private Integer simpTextLen = 4;
    // 算术类型的验证码长度（几位数运算）
    private Integer arithmeticLen = 2;
    // 内置字体设置（可选范围：0 ~ 9 一共存在10种内置字体）只有简单验证码和gif有效果
    private Integer builtInFonts = 0;
    // 自定义验证码字体样式，若设置customFont自定义字体，则优先级高于内置builtInFonts字体设置
    private Font customFont = null;
    // 输出的验证码是否为Base64的方式被输出
    private boolean isBase64 = false;

    /***
     * 构建对象，第一步必须构建对象才可以调用里面的验证码生成
     * @return ValidateCodeUtils验证码工具
     */
    public static SimpleCaptchaUtils getSimpleCaptchaUtils() {
        return new SimpleCaptchaUtils();
    }

    /***
     * 生成一个简单的验证码（Session方式存储）
     * 以图片的方式输出到前端，并以标签img <img src="http://localhost:8080/captcha"/> 的方式显示
     * @param request 请求信息
     * @param response 响应信息
     * @return 返回Base64信息（若isBase64为true则返回，否则为""）
     */
    public String specCaptchaSession(HttpServletRequest request, HttpServletResponse response) {
        SpecCaptcha specCaptcha = new SpecCaptcha(width, height, simpTextLen);
        setFont(specCaptcha);
        return outSession(request, response, specCaptcha);
    }

    /***
     * 生成一个gif闪图的验证码（Session方式存储）
     * 以图片的方式输出到前端，并以标签img <img src="http://localhost:8080/captcha"/> 的方式显示
     * @param request 请求信息
     * @param response 响应信息
     * @return 返回Base64信息（若isBase64为true则返回，否则为""）
     */
    public String gifCaptchaSession(HttpServletRequest request, HttpServletResponse response) {
        GifCaptcha gifCaptcha = new GifCaptcha(width, height, simpTextLen);
        setFont(gifCaptcha);
        return outSession(request, response, gifCaptcha);
    }

    /***
     * 生成一个中文汉字的验证码（Session方式存储）
     * 以图片的方式输出到前端，并以标签img <img src="http://localhost:8080/captcha"/> 的方式显示
     * @param request 请求信息
     * @param response 响应信息
     * @return 返回Base64信息（若isBase64为true则返回，否则为""）
     */
    public String chineseCaptchaSession(HttpServletRequest request, HttpServletResponse response) {
        return outSession(request, response, new ChineseCaptcha(width, height, simpTextLen));
    }

    /***
     * 生成一个算数类型的验证码（Session方式存储）
     * 以图片的方式输出到前端，并以标签img <img src="http://localhost:8080/captcha"/> 的方式显示
     * @param request 请求信息
     * @param response 响应信息
     * @return 返回Base64信息（若isBase64为true则返回，否则为""）
     */
    public String arithmeticCaptchaSession(HttpServletRequest request, HttpServletResponse response) {
        // 算术类型
        ArithmeticCaptcha captcha = new ArithmeticCaptcha(width, height, arithmeticLen);
        log.info("当前算术验证码的算术公式为：{}", captcha.getArithmeticString());
        return outSession(request, response, captcha);
    }

    /***
     * 生成一个简单的验证码（生成的信息存储在ValidateCodeInfo，后面Controller写成和codeValue缓存）
     * @param response 响应信息
     * @return ValidateCodeInfo生成的验证码信息
     */
    public CaptchaInfo specCaptchaSeparate(HttpServletResponse response) {
        SpecCaptcha specCaptcha = new SpecCaptcha(width, height, simpTextLen);
        setFont(specCaptcha);
        return outSeparate(response, specCaptcha);
    }

    /***
     * 生成一个gif类型的验证码（生成的信息存储在ValidateCodeInfo，后面Controller写成和codeValue缓存）
     * @param response 响应信息
     * @return ValidateCodeInfo生成的验证码信息
     */
    public CaptchaInfo gifCaptchaSeparate(HttpServletResponse response) {
        GifCaptcha gifCaptcha = new GifCaptcha(width, height, simpTextLen);
        setFont(gifCaptcha);
        return outSeparate(response, gifCaptcha);
    }

    /***
     * 生成一个中文汉字类型的验证码（生成的信息存储在ValidateCodeInfo，后面Controller写成和codeValue缓存）
     * @param response 响应信息
     * @return ValidateCodeInfo生成的验证码信息
     */
    public CaptchaInfo chineseCaptchaSeparate(HttpServletResponse response) {
        return outSeparate(response, new ChineseCaptcha(width, height, simpTextLen));
    }

    /***
     * 生成一个算数类型的验证码（生成的信息存储在ValidateCodeInfo，后面Controller写成和codeValue缓存）
     * @param response 响应信息
     * @return ValidateCodeInfo生成的验证码信息
     */
    public CaptchaInfo arithmeticCaptchaSeparate(HttpServletResponse response) {
        // 算术类型
        ArithmeticCaptcha captcha = new ArithmeticCaptcha(width, height, arithmeticLen);
        log.info("当前算术验证码的算术公式为：{}", captcha.getArithmeticString());
        return outSeparate(response, captcha);
    }

    /***
     * 生成验证码的基本封装（前后端分离项目的方式，把生成的码返回给Controller，由它保存Redis）
     * @param response 响应信息
     * @param captcha 验证码信息
     * @return ValidateCodeInfo生成的验证码信息
     */
    public CaptchaInfo outSeparate(HttpServletResponse response, Captcha captcha) {
        // 设置简单验证码类型；只有SpecCaptcha和GifCaptcha设置才有效果
        captcha.setCharType(simpTextType);
        // 创建验证码信息
        CaptchaInfo captchaInfo = new CaptchaInfo();
        captchaInfo.setCodeValue(captcha.text());
        captchaInfo.setCodeKey(UUID.randomUUID().toString().replace("-", ""));
        // 是否以Base64的方式返回，还是设置头信息为图片信息，后期在Controller写成图片
        if (isBase64) {
            // 设置base64信息
            captchaInfo.setBase64Str(captcha.toBase64());
        } else {
            // 设置请求头为输出图片类型
            response.setContentType("image/gif");
            response.setHeader("Pragma", "No-cache");
            response.setHeader("Cache-Control", "no-cache");
            response.setDateHeader("Expires", 0);
            captchaInfo.setCaptcha(captcha);
        }
        // 返回最终生成的验证码信息
        return captchaInfo;
    }

    /***
     * 生成验证码的基本封装（Session方式）
     * @param request 请求信息
     * @param response 响应信息
     * @param captcha 验证码接口
     * @return 返回Base64信息（若isBase64为true则返回，否则为""）
     */
    private String outSession(HttpServletRequest request, HttpServletResponse response, Captcha captcha) {
        // 设置简单验证码类型；只有SpecCaptcha和GifCaptcha设置才有效果
        captcha.setCharType(simpTextType);
        // 获取验证码，并且把验证码存储到Session中
        String validateCode = captcha.text().toLowerCase();
        request.getSession().setAttribute(SESSION_ATTR_NAME, validateCode);
        log.info("验证码生成完毕，当前生成的验证码信息为：{}", validateCode);
        // 是否以Base64的方式返回，还是以图片的方式返回
        if (isBase64) {
            // 返回base64的方式
            return captcha.toBase64();
        } else {
            try {
                // 设置请求头为输出图片类型
                response.setContentType("image/gif");
                response.setHeader("Pragma", "No-cache");
                response.setHeader("Cache-Control", "no-cache");
                response.setDateHeader("Expires", 0);
                // 输出图片流
                captcha.out(response.getOutputStream());
            } catch (IOException e) {
                log.info("验证码写成图片失败：{}", e.getMessage());
            }
            return "";
        }
    }

    /***
     * 设置字体样式（若存在自定义Font则优先使用，否则使用内置）
     * @param captcha 验证码对象
     */
    private void setFont(Captcha captcha) {
        try {
            // 设置字体信息
            if (customFont != null) {
                captcha.setFont(customFont);
            } else {
                captcha.setFont(builtInFonts);
            }
        } catch (Exception e) {
            log.info("验证码生成时设置字体样式出现异常：{}", e.getMessage());
        }
    }

    /***
     * 验证码信息(当为分布式项目或者前后端分离时才用此类)
     */
    @Getter
    @Setter
    public static class CaptchaInfo {
        private String codeKey;         // 验证码key
        private String codeValue;       // 验证码value
        private String base64Str;       // base64数据（只有当isBase64为true才有数据）
        private Captcha captcha;        // 验证码类信息
    }
}
